#include <stdexcept>
#include "instructions/math/math_instructions.h"

namespace jvm {
namespace instructions {

// Add instructions
void DADD::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popDouble();
    auto v1 = stack->popDouble();
    stack->pushDouble(v1 + v2);
}

void FADD::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popFloat();
    auto v1 = stack->popFloat();
    stack->pushFloat(v1 + v2);
}

void IADD::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 + v2);
}

void LADD::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 + v2);
}

// Subtract instructions
void DSUB::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popDouble();
    auto v1 = stack->popDouble();
    stack->pushDouble(v1 - v2);
}

void FSUB::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popFloat();
    auto v1 = stack->popFloat();
    stack->pushFloat(v1 - v2);
}

void ISUB::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 - v2);
}

void LSUB::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 - v2);
}

// Multiply instructions
void DMUL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popDouble();
    auto v1 = stack->popDouble();
    stack->pushDouble(v1 * v2);
}

void FMUL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popFloat();
    auto v1 = stack->popFloat();
    stack->pushFloat(v1 * v2);
}

void IMUL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 * v2);
}

void LMUL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 * v2);
}

// Divide instructions
void DDIV::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popDouble();
    auto v1 = stack->popDouble();
    stack->pushDouble(v1 / v2);
}

void FDIV::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popFloat();
    auto v1 = stack->popFloat();
    stack->pushFloat(v1 / v2);
}

void IDIV::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    if (v2 == 0) {
        throw std::runtime_error("java.lang.ArithmeticException: / by zero");
    }
    stack->pushInt(v1 / v2);
}

void LDIV::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    if (v2 == 0) {
        throw std::runtime_error("java.lang.ArithmeticException: / by zero");
    }
    stack->pushLong(v1 / v2);
}

// Remainder instructions
void DREM::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popDouble();
    auto v1 = stack->popDouble();
    stack->pushDouble(std::fmod(v1, v2));
}

void FREM::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popFloat();
    auto v1 = stack->popFloat();
    stack->pushFloat(std::fmod(v1, v2));
}

void IREM::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    if (v2 == 0) {
        throw std::runtime_error("java.lang.ArithmeticException: / by zero");
    }
    stack->pushInt(v1 % v2);
}

void LREM::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    if (v2 == 0) {
        throw std::runtime_error("java.lang.ArithmeticException: / by zero");
    }
    stack->pushLong(v1 % v2);
}

// Negate instructions
void DNEG::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto val = stack->popDouble();
    stack->pushDouble(-val);
}

void FNEG::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto val = stack->popFloat();
    stack->pushFloat(-val);
}

void INEG::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto val = stack->popInt();
    stack->pushInt(-val);
}

void LNEG::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto val = stack->popLong();
    stack->pushLong(-val);
}

// Bitwise AND instructions
void IAND::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 & v2);
}

void LAND::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 & v2);
}

// Bitwise OR instructions
void IOR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 | v2);
}

void LOR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 | v2);
}

// Bitwise XOR instructions
void IXOR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    stack->pushInt(v1 ^ v2);
}

void LXOR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popLong();
    auto v1 = stack->popLong();
    stack->pushLong(v1 ^ v2);
}

// Shift instructions
void ISHL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    auto s = static_cast<uint32_t>(v2) & 0x1f;
    stack->pushInt(v1 << s);
}

void ISHR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    auto s = static_cast<uint32_t>(v2) & 0x1f;
    stack->pushInt(v1 >> s);
}

void IUSHR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popInt();
    auto s = static_cast<uint32_t>(v2) & 0x1f;
    stack->pushInt(static_cast<int32_t>(static_cast<uint32_t>(v1) >> s));
}

void LSHL::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popLong();
    auto s = static_cast<uint32_t>(v2) & 0x3f;
    stack->pushLong(v1 << s);
}

void LSHR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popLong();
    auto s = static_cast<uint32_t>(v2) & 0x3f;
    stack->pushLong(v1 >> s);
}

void LUSHR::execute(std::shared_ptr<rtda::Frame> frame) {
    auto stack = frame->getOperandStack();
    auto v2 = stack->popInt();
    auto v1 = stack->popLong();
    auto s = static_cast<uint32_t>(v2) & 0x3f;
    stack->pushLong(static_cast<int64_t>(static_cast<uint64_t>(v1) >> s));
}

void IINC::fetchOperands(std::shared_ptr<BytecodeReader> reader) {
    _index = static_cast<uint32_t>(reader->readUint8());
    _const = static_cast<int32_t>(reader->readInt8());
}

void IINC::execute(std::shared_ptr<rtda::Frame> frame) {
    auto&& localVars = frame->getLocalVars();
    int32_t val = localVars.getInt(_index);
    val += _const;
    localVars.setInt(_index, val);
}

void IINC::setIndex(uint32_t index) { _index = index; }
void IINC::setConst(int32_t constant) { _const = constant; }

uint32_t IINC::getIndex() const { return _index; }
int32_t IINC::getConst() const { return _const; }

} // namespace instructions
} // namespace jvm